/*
 * Copyright (c) 2009 Enio Perpetuo
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 * 		http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 *  
 */
package co.za.gvi.servlet;

import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Field;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.digester.Digester;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import co.za.gvi.bind.CustomDataType;
import co.za.gvi.handler.IncomeMessageHandler;
import co.za.gvi.message.ResponseMessage;
import co.za.gvi.message.SmsMessage;

/**
 * This class is responsible for listening for <code>IncomeMessages</code> at the
 * call-back endpoint defined for your application. Then it parses and delegates 
 * the message to the respective method implemented in the <code>IncomeMessageHandler</code>.
 * <p>
 * This <code>Servlet</code> must be configured in the <code>web.xml</code> file.
 * <br>
 * And you must set the parameters:
 * <ul>
 *   <li>incomeMessageHandler - The class name for the custom implementation of co.za.gvi.handler.IncomeMessageHandler</li>
 *   <li>customData - The class name for the custom implementation of co.za.gvi.bind.CustomDataType (optional)</li>
 * </ul>
 * <p>
 * Example:
 * 
 * <pre>
 * &lt;servlet>
 *  &lt;servlet-name>incomeMessageServlet&lt;/servlet-name>
 *  &lt;servlet-class>co.za.gvi.servlet.IncomeMessageServlet&lt;/servlet-class>
 *  &lt;init-param>
 *    &lt;description>The class name for the custom implementation of co.za.gvi.handler.IncomeMessageHandler&lt;/description>
 *    &lt;param-name>incomeMessageHandler&lt;/param-name>
 *    &lt;param-value>co.za.gvi.example.receive.IncomeMessagePrinter&lt;/param-value>
 *  &lt;/init-param>
 *  &lt;init-param>
 *    &lt;description>The class name for the custom implementation of co.za.gvi.bind.CustomDataType if any&lt;/description>
 *    &lt;param-name>customData&lt;/param-name>
 *    &lt;param-value>co.za.gvi.example.EmployeeCustomData&lt;/param-value>
 *  &lt;/init-param>
 * &lt;/servlet>
 *
 * &lt;servlet-mapping>
 *   &lt;servlet-name>incomeMessageServlet&lt;/servlet-name>
 *   &lt;url-pattern>/sms&lt;/url-pattern>
 * &lt;/servlet-mapping>
 * </pre>
 * 
 * @see co.za.gvi.handler.IncomeMessageHandler
 * 
 * @author Enio Perpetuo
 */
public class IncomeMessageServlet extends HttpServlet {

	private static final long serialVersionUID = -6734967308407248301L;
	
	private static final String MESSAGE_HANDLER_PARAM = "incomeMessageHandler";
	private static final String CUSTOM_DATA_INIT_PARAM = "customData";

	// Custom implementation of UserDefinedDataType 
	private Class<? extends CustomDataType> customData;
	
	// Custom implementation of IncomeMessageHandler 
	private IncomeMessageHandler incomeMessageHandler;

	@Override
	@SuppressWarnings("unchecked")
	public void init(ServletConfig config) throws ServletException {
        try {
    		super.init(config);
    		// Instantiate Income Message Handler
            String imhClassName = config.getInitParameter(MESSAGE_HANDLER_PARAM);
        	this.incomeMessageHandler = (IncomeMessageHandler)Class.forName(imhClassName).newInstance();
        	
    		// Instantiate User Defined Data Type
            String uddtClassName = config.getInitParameter(CUSTOM_DATA_INIT_PARAM);
            if(uddtClassName != null) {
            	this.customData = (Class<? extends CustomDataType>) Class.forName(uddtClassName);
            }
		} catch (Exception e) {
			throw new ServletException(e);
		}
	}

	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response) 
	throws ServletException, IOException {
		try {
			String xml = request.getParameter("XML");
			Object message = parseInputSource(new InputSource(new StringReader(xml)));
			
			if(message instanceof SmsMessage) {
				incomeMessageHandler.onSmsMessage((SmsMessage)message);
			} else {
				ResponseMessage responseMessage = (ResponseMessage)message;
				String type = responseMessage.getResponseType().toUpperCase();

				// Delegates the handling of a Reply Message
				if (type.equals(ResponseMessage.Type.REPLY.name())) {
					incomeMessageHandler.onReplyMessage(responseMessage);
					
				// Delegates the handling of an Error Message
				} else if (type.equals(ResponseMessage.Type.ERROR.name())) {
					incomeMessageHandler.onErrorMessage(responseMessage);

				// Delegates the handling of a Receipt Message
				} else if (type.equals(ResponseMessage.Type.RECEIPT.name())) {
					incomeMessageHandler.onReceiptMessage(responseMessage);
				}
			}
		} catch (Exception e) {
			throw new ServletException(e);
		}
	}

	/**
	 * This method parses the XML input source.
	 * 
	 * @param inputSource The input source
	 * @return The Message
	 * 
	 * @throws IOException if an I/O error occurs while reading the input stream.
	 * @throws SAXException if an SAX error occurs while parsing the XML.
	 */
	protected Object parseInputSource(InputSource inputSource)
	throws IOException, SAXException {
		Digester digester = new Digester();
		digester.setValidating(false);
		
		// Parse Sms Message
		digester.addObjectCreate("gviSms", SmsMessage.class);
		digester.addBeanPropertySetter("gviSms/smsDateTime", "smsDateTime");
		digester.addBeanPropertySetter("gviSms/gatewayIdentifier", "gatewayIdentifier");
		digester.addBeanPropertySetter("gviSms/cellNumber", "cellNumber");
		digester.addBeanPropertySetter("gviSms/smsLocation", "smsLocation");
		digester.addBeanPropertySetter("gviSms/content", "content");
		
		// Parse Response Message
		digester.addObjectCreate("gviSmsResponse", ResponseMessage.class);
		digester.addBeanPropertySetter("gviSmsResponse/responseDateTime", "responseDateTime");
		digester.addBeanPropertySetter("gviSmsResponse/recipient/msisdn", "msisdn");
		digester.addBeanPropertySetter("gviSmsResponse/responseType", "responseType");
		digester.addBeanPropertySetter("gviSmsResponse/response", "response");
		digester.addBeanPropertySetter("gviSmsResponse/status/code", "statusCode");
		digester.addBeanPropertySetter("gviSmsResponse/status/reason", "statusReason");
		if(customData != null) {
			digester.addObjectCreate("gviSmsResponse/customData", this.customData);
			Field[] fields = customData.getDeclaredFields();
			for (Field field : fields) {
				digester.addBeanPropertySetter("gviSmsResponse/customData/" + field.getName());
			}
			digester.addSetNext("gviSmsResponse/customData", "setCustomData");
		}
		
		return digester.parse(inputSource);
	}
}
